package com.predic8.membrane.annot.generator;

import java.io.BufferedWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import javax.annotation.processing.FilerException;
import javax.annotation.processing.ProcessingEnvironment;
import javax.lang.model.element.Element;
import javax.lang.model.element.TypeElement;
import javax.tools.FileObject;

import com.predic8.membrane.annot.AnnotUtils;
import com.predic8.membrane.annot.model.AttributeInfo;
import com.predic8.membrane.annot.model.ChildElementDeclarationInfo;
import com.predic8.membrane.annot.model.ChildElementInfo;
import com.predic8.membrane.annot.model.ElementInfo;
import com.predic8.membrane.annot.model.MainInfo;
import com.predic8.membrane.annot.model.Model;

public class Parsers {

	private final ProcessingEnvironment processingEnv;

	public Parsers(ProcessingEnvironment processingEnv) {
		this.processingEnv = processingEnv;
	}

	public void writeParserDefinitior(Model m) throws IOException {

		for (MainInfo main : m.getMains()) {
			List<Element> sources = new ArrayList<Element>();
			sources.addAll(main.getInterceptorElements());
			sources.add(main.getElement());

			try {
				FileObject o = processingEnv.getFiler().createSourceFile(
						main.getAnnotation().outputPackage() + ".NamespaceHandlerAutoGenerated",
						sources.toArray(new Element[0]));
				BufferedWriter bw = new BufferedWriter(o.openWriter());
				try {
					bw.write("/* Copyright 2012,2013 predic8 GmbH, www.predic8.com\r\n" +
							"\r\n" +
							"   Licensed under the Apache License, Version 2.0 (the \"License\");\r\n" +
							"   you may not use this file except in compliance with the License.\r\n" +
							"   You may obtain a copy of the License at\r\n" +
							"\r\n" +
							"   http://www.apache.org/licenses/LICENSE-2.0\r\n" +
							"\r\n" +
							"   Unless required by applicable law or agreed to in writing, software\r\n" +
							"   distributed under the License is distributed on an \"AS IS\" BASIS,\r\n" +
							"   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r\n" +
							"   See the License for the specific language governing permissions and\r\n" +
							"   limitations under the License. */\r\n" +
							"\r\n" +
							"package " + main.getAnnotation().outputPackage() + ";\r\n" +
							"\r\n" +
							"/**\r\n" +
							"  * Automatically generated by " + Parsers.class.getName() + ".\r\n" +
							"  */\r\n" +
							"public class NamespaceHandlerAutoGenerated {\r\n" +
							"\r\n" +
							"	public static void registerBeanDefinitionParsers(NamespaceHandler nh) {\r\n");
					for (ElementInfo i : main.getIis()) {
						if (i.getAnnotation().topLevel()) {
							bw.write("		nh.registerGlobalBeanDefinitionParser(\"" + i.getAnnotation().name() + "\", new " + i.getParserClassSimpleName() + "());\r\n");
						} else {
							for (ChildElementDeclarationInfo cedi : i.getUsedBy()) {
								for (ChildElementInfo cei : cedi.getUsedBy()) {
									TypeElement element = cei.getEi().getElement();
									String clazz = AnnotUtils.getRuntimeClassName(element);
									bw.write("		nh.registerLocalBeanDefinitionParser(\"" + clazz + "\", \"" + i.getAnnotation().name() + "\", new " + i.getParserClassSimpleName() + "());\r\n");
								}
							}
						}
					}
					bw.write(
							"	}\r\n" +
									"}\r\n" +
							"");
				} finally {
					bw.close();
				}
			} catch (FilerException e) {
				if (e.getMessage().contains("Source file already created"))
					return;
				throw e;
			}
		}
	}

	public void writeParsers(Model m) throws IOException {
		for (MainInfo main : m.getMains()) {
			for (ElementInfo ii : main.getIis()) {
				List<Element> sources = new ArrayList<Element>();
				sources.add(main.getElement());
				sources.add(ii.getElement());

				String interceptorClassName = ii.getElement().getQualifiedName().toString();

				try {
					FileObject o = processingEnv.getFiler().createSourceFile(main.getAnnotation().outputPackage() + "." + ii.getParserClassSimpleName(),
							sources.toArray(new Element[0]));
					BufferedWriter bw = new BufferedWriter(o.openWriter());
					try {
						bw.write("/* Copyright 2012 predic8 GmbH, www.predic8.com\r\n" +
								"\r\n" +
								"   Licensed under the Apache License, Version 2.0 (the \"License\");\r\n" +
								"   you may not use this file except in compliance with the License.\r\n" +
								"   You may obtain a copy of the License at\r\n" +
								"\r\n" +
								"   http://www.apache.org/licenses/LICENSE-2.0\r\n" +
								"\r\n" +
								"   Unless required by applicable law or agreed to in writing, software\r\n" +
								"   distributed under the License is distributed on an \"AS IS\" BASIS,\r\n" +
								"   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r\n" +
								"   See the License for the specific language governing permissions and\r\n" +
								"   limitations under the License. */\r\n" +
								"\r\n" +
								"package " + main.getAnnotation().outputPackage() + ";\r\n" +
								"\r\n" +
								"import org.w3c.dom.Element;\r\n" +
								"import org.springframework.beans.factory.xml.ParserContext;\r\n" +
								"import org.springframework.beans.factory.support.BeanDefinitionBuilder;\r\n");
						if (!main.getAnnotation().outputPackage().equals("com.predic8.membrane.core.config.spring"))
							bw.write("import com.predic8.membrane.core.config.spring.*;\r\n");
						bw.write(
								"\r\n" +
										"/**\r\n" +
										"  * Automatically generated by " + Parsers.class.getName() + ".\r\n" +
										"  */\r\n" +
										"public class " + ii.getParserClassSimpleName() + " extends AbstractParser {\r\n" +
										"\r\n" +
										"	protected Class<?> getBeanClass(org.w3c.dom.Element element) {\r\n" +
										"		return " + interceptorClassName + ".class;\r\n" +
								"	}\r\n");
						bw.write("	@Override\r\n" +
								"	protected void doParse(Element element, ParserContext parserContext, BeanDefinitionBuilder builder) {\r\n");
						if (ii.isHasIdField())
							bw.write("		setPropertyIfSet(\"id\", element, builder);\r\n");
						bw.write(
								"		setIdIfNeeded(element, parserContext, \"" + ii.getAnnotation().name() + "\");\r\n");
						for (AttributeInfo ai : ii.getAis()) {
							if (ai.getXMLName().equals("id"))
								continue;
							if (ai.isBeanReference(processingEnv.getTypeUtils())) {
								if (!ai.isRequired())
									bw.write("		if (element.hasAttribute(\"" + ai.getXMLName() + "\"))\r\n");
								bw.write("		builder.addPropertyReference(\"" + ai.getSpringName() + "\", element.getAttribute(\"" + ai.getXMLName() + "\"));\r\n");
							} else {
								bw.write("		setProperty" + (ai.isRequired() ? "" : "IfSet") + "(\"" + ai.getXMLName() + "\", \"" + ai.getSpringName() + "\", element, builder" + (ai.isEnum(processingEnv.getTypeUtils()) ? ", true" : "") + ");\r\n");
							}
							if (ai.getXMLName().equals("name"))
								bw.write("		element.removeAttribute(\"name\");\r\n");
						}
						if (ii.getOai() != null) {
							bw.write("		setProperties(\"" + ii.getOai().getSpringName() + "\", element, builder);\r\n");
						}
						for (ChildElementInfo cei : ii.getCeis())
							if (cei.isList())
								bw.write("		builder.addPropertyValue(\"" + cei.getPropertyName() + "\", new java.util.ArrayList<Object>());\r\n");
						if (ii.getTci() != null)
							bw.write("		builder.addPropertyValue(\"" + ii.getTci().getPropertyName() + "\", element.getTextContent());\r\n");
						else
							bw.write("		parseChildren(element, parserContext, builder);\r\n");
						for (ChildElementInfo cei : ii.getCeis())
							if (cei.isList() && cei.isRequired()) {
								bw.write("		if (builder.getBeanDefinition().getPropertyValues().getPropertyValue(\"" + cei.getPropertyName() + "[0]\") == null)\r\n");
								bw.write("			throw new RuntimeException(\"Property '" + cei.getPropertyName() + "' is required, but none was defined (empty list).\");\r\n");
							}

						bw.write(
								"	}\r\n" +
								"");

						bw.write(
								"@Override\r\n" +
								"protected void handleChildObject(Element ele, ParserContext parserContext, BeanDefinitionBuilder builder, Class<?> clazz, Object child) {\r\n");
						for (ChildElementInfo cei : ii.getCeis()) {
							bw.write(
									"	if (" + cei.getTypeDeclaration().getQualifiedName() + ".class.isAssignableFrom(clazz)) {\r\n" +
											"		setProperty(builder, \"" + cei.getPropertyName() + "\"" + (cei.isList() ? "+\"[\"+ incrementCounter(builder, \"" + cei.getPropertyName() + "\") + \"]\" " : "") + ", child);\r\n" +
									"	} else \r\n");
						}
						bw.write(
								"	{\r\n" +
										"		throw new RuntimeException(\"Unknown child class \\\"\" + clazz + \"\\\".\");\r\n" +
								"	}\r\n");
						bw.write(
								"}\r\n");

						bw.write(
								"}\r\n" +
								"");
					} finally {
						bw.close();
					}
				} catch (FilerException e) {
					if (e.getMessage().contains("Source file already created"))
						return;
					throw e;
				}

			}
		}

	}


}
